UART Custom Adapters
The UART custom protocols
The UART custom adapters use two protocols - PCustomUartControl
and PCustomUartCommunication
.
The PCustomUartControl
protocol class is responsible for the actor configuration and consists of the following messages:
-
configureUart(DCustomUartConfiguration)
- Configure UART communication. For details see the Part 3 - Configuring the UART custom actor. -
enableReceiveData()
- Start data reception. -
disableReceiveData()
- Stop data reception. Depending on the underlying implementation the next received packet might still be delivered. -
configurationComplete()
- Reply once the configuration is completed. -
bufferOverflow()
- Dispatched when a buffer overflow error occurs.
The data from the UART interface is transferred over the PCustomUartCommunication
protocol class.
-
sendPacket(DCustomUartNotification)
- Send a data package. -
freeRxBuffer(uint8 ref)
- Tell the adapter to free the buffer pointed by the pointer in the message. -
receivedPacket(DCustomUartNotification)
- Notify when a data packet is received. The received length will be stored in thedataLength
field of the transition data. -
sendDone()
- Response tosendPacket
.
The transmitted and received packets are contained in the DCustomUartNotification
data class, which consists of the following data types:
-
bufferPtr
: a pointer touint8
rx/tx buffer -
dataLength
:uint32
length of the data pointed by thebufferPtr
.
Recommendations
While the UART custom adapters may be used directly from within CaGe (using the PCustomUartControl
and PCustomUartCommunication
protocol) we strongly recommend to create a wrapping or intermediate actor which handles message en- and decoding. This keeps the test cases free from en-/decoding/crc/…-logic. We also recommend that the actor handles the UART configuration. An example structure could look like this:
Usage
The chunk handler
The data received in the receive buffer at first goes through the chunk handler. The user can implement there the first stage of the received data handling, as shown in the examples below (Part 1 - Standard connection to the low-level code and Part 2 - Pre-filtering in the chunk handler). The chunk handler is basically a callback function which is called under two conditions:
-
If a UART receiver timeout occurs (i.e. when the bus has been idle for the configured number of bit times)
-
If the last set chunk size was reached
It must return an integer value with the following semantics:
-
If
returnValue > 0
: Receive the next chunk with length = returnValue bytes (or until receiver timeout) -
If
returnValue <= 0
: The current buffer should be split at the returned offset (based on the last received byte). The side before the split is considered to be a complete message. The side after the split is the start of the next message.-
Examples:
-
if
0
is returned the whole buffer is a message -
if
-5
is returned the last 5 byte of the current buffer will be part of the next message. All bytes before that belong to the completed message.
-
-
it is not possible to discard buffers from within the chunk handler. You may however return 0 to switch buffers. The message may then be discarded within the receiving actor.
-
The chunk handler is running on interrupt level. It should therefore not do too much work. You should do the heavy lifting once the message is delivered to the model (room actor transition). |
The Example Code
The following sections describe how to use the UART custom adapter to implement an example user protocol. Parts 1 to 3 focus on the actor’s configuration. Part 4 contains the full actor implementation, including a basic message handling.
Part 1 - Standard connection to the low-level code
The first step in configuring the UART actor is to define receive and transmit buffers, as well as the previously mentioned chunk handler - i.e. the callback function that pre-filters the incoming bytes. In this example the bytes are not checked, but instead will be handed over directly to the actor containing the chunk handler. The actor receives the bytes in the transition data of the receivedPacket
message.
The buffers declared in the code below are used later in the ROOM actor.
All the buffers must be declared in the DMA memory section by using the "attribute" directive, as shown in the example. Otherwise the low-level code will NOT work! |
Structure {
usercode1 '''
#include "uart/Uart.h" // contains standard UART definitions
'''
usercode3 '''
// Initialize 6 receive buffers, each 255 byte long.
// Initialize 1 txBuffer that is 50 byte long.
static uint8_t __attribute__((section(".dmaMemSection_D2"))) rxBuffers[6][255];
static uint8_t __attribute__((section(".dmaMemSection_D2"))) txBuffer[50];
static int16_t chunkHandler(uint8_t* buffer, uint8_t newDataLength) {
// This function is called when a timeout
// in data reception occurs or when the receive buffer is full.
return 0;
}
'''
...
}
Part 2 - Pre-filtering in the chunk handler
As mentioned in Example 1, the chunk handler can do some pre-filtering of the incoming data. In this simple example, the chunk handler tries to find the "new line" character "\n" in the current buffer. If the function finds "\n", it computes how many characters are still left in the buffer. It then returns either a 0
or a negative number (number of the excessive bytes). However, if no "\n" character is found, the chunk handler returns 20
- a positive number telling the low-level logic to receive the next 20
bytes and the receivedPacket
message is NOT sent.
static int16_t chunkHandler(uint8_t* buffer, uint8_t newDataLength) {
for (uint8_t i = 0; i < newDataLength; i++) {
if (buffer[i] == '\n') {
return i - newDataLength;
}
}
return 20; // read the next 20 bytes
}
Part 3 - Configuring the UART custom actor
An actor is configured by the DCustomUartConfiguration
data class, which contains the following fields:
-
uartConfig
- it is a structure consisting of the standard UART parameters:-
uartBaudrate
-
uartParity
-
uartStopbits
-
uartDatabits
-
-
rxBufferPtrs
- contains up to 6 pointers to the receive buffers -
noOfRxBuffers
- number of the above buffers that are actually used -
rxBufferLength
- if the number of the received bytes is equal to this number, the chunk handler is called and the receive buffers are switched -
initialChunkSize
- after this amount of received bytes, the chunk handler will be called for the first time. The next calls occur according to the values returned by the chunk handler function. -
nextChunkHandler
- pointer to the chunk handler function -
receiverTimeout
- after this time (the time unit is the bit duration of the current baud rate) the chunk handler will be called if no data is received
An example UART configuration is shown below.
DCustomUartConfiguration cfg = {
.uartConfig = {
.uartBaudrate = 115200,
.uartParity = EParity_NONE,
.uartStopbits = EStopBits_STOPBITS_1,
.uartDatabits = EUartDataBits_DATA_BITS_8,
},
.rxBufferPtrs = {rxBuffers[0], rxBuffers[1], rxBuffers[2], rxBuffers[3], rxBuffers[4], rxBuffers[5]},
.noOfRxBuffers = 6,
.rxBufferLength = 255,
.initialChunkSize = 255,
.nextChunkHandler = chunkHandler,
.receiverTimeout = 1*9
};
// send the configuration message over a PCustomUartControl port
uartControl.configureUart(&cfg);
When the configuration is complete, the actor sends a |
Part 4 - Putting it all together - the complete ROOM actor
In this example we use two UART interfaces that talk to each other. The first interface consists of three actors - the low level UART adapter (UART 1 custom), the intermediate actor and the user protocol control logic. The user protocol control logic actor can be replaced by a CaGe test actor.
The other interface is intended to represent a Device Under Test (DUT) and consists of two actors - a tester actor that plays back a number of messages and the low level UART adapter (UART 7 custom).
The wiring
The required wiring for the examples is simple - you only need to connect the corresponding RX and TX signals. We chose UART 1 and UART 7 but you can choose any of the available UART custom actors.
UART 1 | UART 7 | |
---|---|---|
PB7 (RX) |
⇐ |
PE8 (TX) |
PA9 (TX) |
⇒ |
PB3 (RX) |
The intermediate actor
Let us start with the intermediate actor that contains the elements from the previous examples. It is responsible for configuring the UART interface, implementing the chunk handler function, declaring the DMA buffers and encoding/decoding of the data.
In this example, the single "messages" are separated by the #
character. The end of data packet is signalized by the "\n" character. The actor receives the data that comes via the UART 1 port, parses it by removing the delimiters and passes it to the upper layer (user protocol control logic). The actor communicates with the user protocol control logic (see The protocol control logic) over the PMyCustomProtocol
class (described in Using custom protocol classes).
The buffer containing the received data should be freed by sending the |
ActorClass AIntermediateActor {
Interface {
Port uartInterface: PMyCustomProtocol
conjugated Port uartCom: PCustomUartCommunication
conjugated Port uartControl: PCustomUartControl
}
Structure {
usercode1 '''
#include "uart/Uart.h"
'''
usercode3 '''
// Initialize 6 receive buffers, each 255 byte long.
// Initialize 1 txBuffer that is 50 byte long.
// Note that the buffers have to be in the DMA memory section.
static uint8_t __attribute__((section(".dmaMemSection_D2"))) rxBuffers[6][255];
static uint8_t __attribute__((section(".dmaMemSection_D2"))) txBuffer[50];
static uint8_t dataBuffer[100];
static int16_t chunkHandler(uint8_t* buffer, uint8_t newDataLength) {
for (uint8_t i = 0; i < newDataLength; i++) {
if (buffer[i] == '\n') {
return i - newDataLength;
}
}
return 20; // read the next 20 bytes
}
'''
external Port uartCom
external Port uartControl
external Port uartInterface
SAP logger: PLogger
SAP timer: PTimer
}
Behavior {
StateMachine {
State running
Transition init0: initial -> delay {
action '''timer.startTimeout(500);'''
}
Transition tr0: running -> running {
triggers {
<sendData: uartInterface>
}
action '''
for(int i=0; i<transitionData->dataLength; i++) {
txBuffer[i] = transitionData->bufferPtr[i];
}
DCustomUartNotification pkt = {
.bufferPtr = txBuffer,
.dataLength = transitionData->dataLength,
};
uartCom.sendPacket(&pkt);
'''
}
Transition tr1: running -> running {
triggers {
<receivedPacket: uartCom>
}
action '''
uint8_t ctr=0;
// init the data structure that will be sent later
DCustomUartNotification pkt = {
.bufferPtr = 0,
.dataLength = 0,
};
ctr=0;
// This place in code is for additional or
// computationally expensive message parsing.
// extract single messages from the buffer
while(ctr<transitionData->dataLength){
pkt.bufferPtr = (dataBuffer+ctr);
while(ctr<transitionData->dataLength){
// check if the current character is the delimiter
if(transitionData->bufferPtr[ctr] != '#') {
dataBuffer[ctr]=transitionData->bufferPtr[ctr];
ctr++;
}
else {
pkt.dataLength = ctr;
ctr++;
break;
}
}
uartInterface.receivedData(&pkt);
}
uartCom.freeRxBuffer(transitionData->bufferPtr); // free the buffer
'''
}
Transition tr7: running -> running {
triggers {
<bufferOverflow: uartControl>
}
action '''logger.log("UART: Buffer overflow!");'''
}
State configuration
State delay
Transition tr3: configuration -> running {
triggers {
<configurationComplete: uartControl>
}
action '''uartControl.enableReceiveData();
uartInterface.uartReady();'''
}
Transition tr4: delay -> configuration {
triggers {
<timeout: timer>
}
action '''
DCustomUartConfiguration cfg = {
.uartConfig = {
.uartBaudrate = 115200,
.uartParity = EParity_NONE,
.uartStopbits = EStopBits_STOPBITS_1,
.uartDatabits = EUartDataBits_DATA_BITS_8,
},
.rxBufferPtrs = {rxBuffers[0], rxBuffers[1], rxBuffers[2], rxBuffers[3], rxBuffers[4], rxBuffers[5]},
.noOfRxBuffers = 6,
.rxBufferLength = 255,
.initialChunkSize = 255,
.nextChunkHandler = chunkHandler,
.receiverTimeout = 1*9
};
uartControl.configureUart(&cfg);
'''
}
}
}
}
Using custom protocol classes
The user protocol control logic usually requires a custom protocol class. The protocol class used in the examples is shown in the code snippet below. Custom protocol classes allow to structure your data and enable a convenient way to connect to CaGe.
// This is an example protocol. You can define your own protocol(s).
ProtocolClass PMyCustomProtocol {
incoming {
Message sendData(DCustomUartNotification)
}
outgoing {
Message receivedData(DCustomUartNotification)
Message uartReady()
}
}
The protocol control logic
This code snippet contains the protocol control logic. In this example the protocol handling consists merely of printing the received data in the log output and sending some ASCII data to show general principle of operation. In a real test scenario, this actor would be replaced with a CaGe test actor.
ActorClass AProtocolControlLogicActor {
Interface {
conjugated Port uartInterface: PMyCustomProtocol
}
Structure {
Attribute msgBuffer [50]: uint8
external Port uartInterface
SAP timer: PTimer
SAP logger: PLogger
}
Behavior {
StateMachine {
State waitForUartReady
State running
Transition init0: initial -> waitForUartReady {
action '''
timer.startTimer(2000);
'''
}
Transition tr0: waitForUartReady -> running {
triggers {
<uartReady: uartInterface>
}
}
Transition tr1: running -> running {
triggers {
<receivedData: uartInterface>
}
action '''
// You can add your protocol handling here. The below lines only print
// the received data into the logger.
// zero terminate received string and forward it to logger
*(transitionData->bufferPtr + transitionData->dataLength) = '\0';
logger.log((char *)(transitionData->bufferPtr));'''
}
Transition tr2: running -> running {
triggers {
<timeout: timer>
}
action '''
// get the length of the example string, put the contents in the msgBuffer
uint16_t length = snprintf((char *)msgBuffer, 255, "Some test data to be sent\n");
// check if snprintf returned successfully
if (length > 0 && length < 256) {
// prepare the data structure to send
DCustomUartNotification sendReq = {
.bufferPtr = msgBuffer,
.dataLength = length
};
// send the data
uartInterface.sendData(&sendReq);
} else {
logger.log("CustomUartTester: Could not snprintf...");
}
'''
}
}
}
}
The Device Under Test actor
The simple implementation below uses another miniHIL’s UART adapter (UART 7) to implement DUT that sends data over UART periodically and in an alternating manner. The single "messages" are separated by the #
character. The end of a data packet is signalized by the "\n" character.
ActorClass ACustomUartTester {
Interface {
conjugated Port uartCtrl: PCustomUartControl
conjugated Port uartCom: PCustomUartCommunication
}
Structure {
usercode1 '''
#include "uart/Uart.h"
'''
usercode3 '''
static uint8_t __attribute__((section(".dmaMemSection_D2"))) rxBuffers[2][256];
static uint8_t __attribute__((section(".dmaMemSection_D2"))) txBuffer[256];
static int16_t chunkHandler(uint8_t* buffer, uint8_t newDataLength) {
for (uint8_t i = 0; i < newDataLength; i++) {
if (buffer[i] == '\n') {
return i - newDataLength;
}
}
return 20; // read the next 20 bytes
}
'''
external Port uartCom
external Port uartCtrl
Attribute toggleMsg: uint8 = "0"
SAP timer: PTimer
SAP logger: PLogger
}
Behavior {
StateMachine {
State configuring
State active
Transition init0: initial -> delay {
action '''timer.startTimeout(500);'''
}
Transition tr0: configuring -> active {
triggers {
<configurationComplete: uartCtrl>
}
action '''
timer.startTimeout(2000);
uartCtrl.enableReceiveData();'''
}
Transition tr1: active -> active {
triggers {
<receivedPacket: uartCom>
}
action '''
// zero terminate received string and forward it to logger
*(transitionData->bufferPtr + transitionData->dataLength) = '\0';
logger.log((char *)(transitionData->bufferPtr));
uartCom.freeRxBuffer(transitionData->bufferPtr);'''
}
Transition tr2: active -> active {
triggers {
<timeout: timer>
}
action '''
// simulate a pause in the bytestream
timer.startTimeout(1000);
uint16_t length;
// single messages are separated by '#'
if (toggleMsg == 0) {
length = snprintf((char *)txBuffer, 256, "Test message1#Test message2#Test message3\nTest message4#Tes");
toggleMsg = 1;
}
else {
length = snprintf((char *)txBuffer, 256, "t message5#Test message6\n");
toggleMsg = 0;
}
if (length > 0 && length < 256) {
DCustomUartNotification sendReq = {
.bufferPtr = txBuffer,
.dataLength = length
};
uartCom.sendPacket(&sendReq);
} else {
logger.log("CustomUartTester: Could not snprintf...");
}
'''
}
State delay
Transition tr3: delay -> configuring {
triggers {
<timeout: timer>
}
action '''
DCustomUartConfiguration cfg = {
.uartConfig = {
.uartBaudrate = 115200,
.uartDatabits = EUartDataBits_DATA_BITS_8,
.uartParity = EParity_NONE,
.uartStopbits = EStopBits_STOPBITS_1
},
.rxBufferPtrs = {rxBuffers[0], rxBuffers[1]},
.noOfRxBuffers = 2,
.rxBufferLength = 256,
.initialChunkSize = 15,
.nextChunkHandler = chunkHandler,
.receiverTimeout = 5*9 // 5 * 9 Bit
};
uartCtrl.configureUart(&cfg);'''
}
}
}
}